package ru.papergames.battleserver.model.auth;

import org.apache.log4j.Logger;
import ru.papergames.battleserver.model.ConnectingFlashUser;
import ru.papergames.battleserver.model.UsersManager;
import ru.papergames.battleserver.network.clientpackets.flashuser.AuthLogin;
import ru.papergames.battleserver.network.serverpackets.flashuser.AuthTimeout;


/**
 * @author: ATroshanin
 * Date: 15.06.2010
 * Time: 18:06:45
 */
public class AuthThreadController extends Thread {

    private static final Logger log = Logger.getLogger(AuthThreadController.class.getName());

    private static AuthThreadController instance = new AuthThreadController();

    public static final int MAX_WAITING_TIME = 30000;
    public static final int SLEEP_TIME = 100;

    private final UserHolder header;
    private UserHolder tail;
    private final Object tailLock = new Object();

    private volatile boolean shuttingDown;
    private volatile boolean active;
    private int activeUserCnt;

    private final Object lock = new Object();

    public static AuthThreadController getInstance() {
        return instance;
    }

    private AuthThreadController() {
        header = new UserHolder(null);
        tail = header;
        start();
    }

    public boolean addUserToAuthProcess(WaitingForAuthUser user) {

        UserHolder userHolder = new UserHolder(user);

        synchronized (tailLock) {
            userHolder.previous = tail;
            tail.next = userHolder;
            tail = userHolder;
        }
        activeUserCnt++;
        activateIfNotActive();
        return true;
    }

    public void run() {

        while(!shuttingDown) {
            waitIfNotActive();
            if (!shuttingDown) {
                long start = System.currentTimeMillis();
                processUsers();
                long delta = System.currentTimeMillis() - start;

                sleepAfterProcess(SLEEP_TIME - delta);
            }

        }
    }
    
    private void processUsers() {

        try {
            UsersManager usersManager = UsersManager.getInstance();
            UserHolder userHolder = header;
            while((userHolder = userHolder.next) != null) {

                WaitingForAuthUser user = userHolder.getUser();
                ConnectingFlashUser connectingUser = usersManager.popFlashUserFromNewUsers(user.getPlayerId());
                if (connectingUser != null) {
                    removeUserHolder(userHolder);
                    // ok now we sure that web server already registered current user on this messaging server
                    AuthLogin.authorizeUser(user.getClient(), connectingUser, user.getSecretKey());
                    continue;
                }

                if (System.currentTimeMillis() - user.getStartWaitTime() > MAX_WAITING_TIME) {
                    removeUserHolder(userHolder);
                    user.getClient().sendPacket(AuthTimeout.STATIC_PACKET);
                }
                
            } // end while loop
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void activateIfNotActive() {
        if (!active) {
            synchronized (lock) {
                active = true;
                lock.notifyAll();
            }
        }
    }

    private void waitIfNotActive() {
        if (!active) {
            synchronized (lock) {
                if (!active && activeUserCnt == 0) {
                    try {
                        log.info("AuthThreadController.waitIfNotActive. Start waiting");
                        lock.wait();
                        log.info("AuthThreadController.waitIfNotActive. Got users. Starting to work again.");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * We have to synchronize removing userHolder if it is TAIL,
     * because TAIL could be accessed from other threads(threads which add new battles)
     * All other holders are accessed only from current thread, so we can manage them as we want.
     * @param userHolder
     */
    private void removeUserHolder(UserHolder userHolder) {
        if (userHolder.next == null) { // If yes, then userHolder is tail!!!
            synchronized (tailLock) {
                if (userHolder.next == null) {
                    userHolder.previous.next = null;
                    tail = userHolder.previous;
                } else {
                    userHolder.previous.next = userHolder.next;
                    userHolder.next.previous = userHolder.previous;
                }
            }
        } else {
            userHolder.previous.next = userHolder.next;
            userHolder.next.previous = userHolder.previous;
        }
        activeUserCnt--;
        activeUserCnt = activeUserCnt <= 0 ? 0 : activeUserCnt;
        active = activeUserCnt != 0;
    }



    private void sleepAfterProcess(long millis) {
        try {
            sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void shutDown() {
        this.shuttingDown = true;
        synchronized (lock) {
            lock.notify();
        }
    }

}
